﻿using UnityEngine;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using Devil;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace GameToolkit
{

    public class MakeABNode
    {
        public readonly string path;
        public readonly string folder;
        public MakeAB asset;
        public List<MakeABNode> children;
        public HashSet<string> makeAbAssets;

        public MakeABNode upmAsset;

        public MakeABNode()
        {
            folder = "Assets";
            this.path = "";
            children = new List<MakeABNode>();
        }

        public MakeABNode(string path)
        {
            this.path = path;
            this.folder = NativeFileSystem.GetFolderName(path);
        }

        public bool HasChildFolder(string folder)
        {
            if (children == null)
                return false;
            foreach (var s in children)
            {
                if (s.folder == folder)
                    return true;
            }
            return false;
        }

        public static MakeABNode FindNode(MakeABNode root, string assetPath)
        {
            var result = root == null || !assetPath.StartsWith(root.folder) ? null : root;
            var finish = false;
            while (result != null && result.children != null && !finish)
            {
                finish = true;
                for (int i = 0; i < result.children.Count; i++)
                {
                    var tmp = result.children[i];
                    if (assetPath.StartsWithIgnoreCase(tmp.folder))
                    {
                        result = tmp;
                        finish = false;
                        break;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="root"></param>
        /// <param name="node"></param>
        /// <param name="assetPath">如果找到父节点，assetPath 将被修改为子节点资源路径</param>
        /// <returns></returns>
        public static MakeABNode FindParent(MakeABNode root, MakeAB node, ref string assetPath)
        {
            var parent = root;
            bool finish = false;
            while (parent != null && parent.children != null && !finish)
            {
                finish = true;
                for (int i = 0; i < parent.children.Count; i++)
                {
                    var tmp = parent.children[i];
                    if (tmp.asset == node)
                    {
                        assetPath = tmp.path;
                        return parent;
                    }
                    if (assetPath.StartsWithIgnoreCase(tmp.folder))
                    {
                        finish = false;
                        parent = tmp;
                        break;
                    }
                }
            }
            return null;
        }
    }

    [CreateAssetMenu(fileName = "MainAB", menuName = "Make AssetBundle")]
    public partial class MakeAB : ScriptableObject
    {
        public enum EPackMode
        {
            Default = 0,
            //RootFolder,
            //Variant,
            //RelativeFolder,
            Dependent = 1,
            Scene = 2,
            Auto = 3, // include (auto|dependent|dependency) folders as dependnet package
            Streaming = 4, // streaming assets
        }

        [SerializeField, Tooltip("AB 打包规则")]
        EPackMode m_PackageMode;

        [Tooltip("打包名称（匹配符{$n}将匹配n(0<=n<=9)级目录文件夹名并替换，如果文件位于根目录，则自动添加BASE目录，如果n==0，则以文件名替换）")]
        public string m_PackageNamespace;

        //[SerializeField, EnableEdit(EEnableEditMode.Disabled)]
        string mFallbackName;

        [SerializeField, Tooltip("自动包含该目录中依赖的资源")]
        bool m_AutoIncludeDependency;

        //[SerializeField, Tooltip("定义变体文件夹（分隔符： ,;)，变体文件夹中的资源将自动打包为变体")]
        //string m_AutoVariants;

        [Tooltip("忽略该项设置")]
        public bool m_Ignored;

        [Tooltip("排除这个AB资源")]
        public bool m_Excluded;

        [SerializeField, Tooltip("包含 Unity Package 中的资源")]
        bool m_AsUnityPackage;

        [Tooltip("包含的资源文件夹"), TextArea(5, 10)]
        public string m_IncludeFolder = @"^.*$";
        [Tooltip("包含的资源文件"), TextArea(5, 10)]
        public string m_IncludeFile = @"^.*$";

        [Tooltip("排除的资源文件夹"), TextArea(5, 10)]
        public string m_ExcludeFolder;
        [Tooltip("排除的资源文件"), TextArea(5, 10)]
        public string m_ExcludeFile;


        Regex[] mIncludeFilePattern;
        Regex[] mIncludeFolderPattern;
        Regex[] mExcludeFilePattern;
        Regex[] mExcludeFolderPattern;
        readonly Regex packDependentPattern = new Regex(@"(.+/|^)(auto|dependent|dependency)/.+", RegexOptions.IgnoreCase);
        readonly Regex packScenePattern = new Regex(@".+/.+.unity", RegexOptions.IgnoreCase);
        //Regex variantPattern;
        public bool IsUpmAssets => m_AsUnityPackage;

        private void Awake()
        {
            mFallbackName = name;
        }

        //void ISerializationCallbackReceiver.OnAfterDeserialize()
        //{
        //    Prepare(null);
        //}

        //void ISerializationCallbackReceiver.OnBeforeSerialize()
        //{
        //}

        public void Prepare(string fallbackName)
        {
            if (!string.IsNullOrEmpty(fallbackName))
                mFallbackName = fallbackName;
            //var matchs = string.IsNullOrEmpty(m_AutoVariants) ? null :
            //    Regex.Matches(m_AutoVariants, "(?<= |\t|;|,|^)[a-z0-9_]+(?= |\t|;|,|$)", RegexOptions.IgnoreCase);
            //if (matchs != null && matchs.Count > 0)
            //{
            //    var buf = StringUtil.GetBuilder();
            //    buf.Append("(?<=.+/)");
            //    bool first = true;
            //    foreach (Match m in matchs)
            //    {
            //        if (first)
            //            first = false;
            //        else
            //            buf.Append('|');
            //        buf.Append(m.Value);
            //    }
            //    buf.Append("(?=/.+)");
            //    variantPattern = new Regex(StringUtil.ReturnRelease(buf), RegexOptions.IgnoreCase);
            //}
            //else
            //{
            //    variantPattern = null;
            //}
            mIncludeFilePattern = ParseArray(m_IncludeFile, true);
            mIncludeFolderPattern = ParseArray(m_IncludeFolder, true);
            mExcludeFilePattern = ParseArray(m_ExcludeFile, false);
            mExcludeFolderPattern = ParseArray(m_ExcludeFolder, false);
        }

        public EPackMode GetPackType(string asset)
        {
            if(m_PackageMode == EPackMode.Auto)
            {
                if (packDependentPattern.IsMatch(asset))
                    return EPackMode.Dependent;
                else if (packScenePattern.IsMatch(asset))
                    return EPackMode.Scene;
                else
                    return EPackMode.Default;
            }
            else
            {
                return m_PackageMode;
            }
        }

        Regex[] ParseArray(string str, bool defaultInclude)
        {
            List<Regex> lst = new List<Regex>();
            if (!string.IsNullOrEmpty(str))
            {
                var strs = str.Split('\n');
                lst = new List<Regex>(strs.Length);
                for (int i = 0; i < strs.Length; i++)
                {
                    strs[i] = strs[i].Trim();
                    if (strs[i].Length > 0)
                        lst.Add(new Regex(strs[i], RegexOptions.IgnoreCase));
                }
            }
            if (lst.Count == 0 && defaultInclude)
                lst.Add(new Regex(".*"));
            return lst.ToArray();
        }

        public bool IsIncludeAsset(string rootFolder, string assetPath, string dependentBy)
        {
            if (m_Excluded)
                return false;
            var pack = GetPackType(assetPath);
            if (pack == EPackMode.Scene && !assetPath.EndsWith(".unity"))
                return false;
            if (pack == EPackMode.Dependent && string.IsNullOrEmpty(dependentBy))
                return false;
            var dic = NativeFileSystem.GetFolderName(assetPath);
            if (dic.Length > rootFolder.Length + 1)
                dic = dic.Substring(rootFolder.Length + 1);
            else
                dic = ".";
            foreach (var f in mExcludeFolderPattern)
            {
                if(f.IsMatch(dic))
                    return false;
            }
            var file = NativeFileSystem.GetFileName(assetPath, false);
            foreach (var f in mExcludeFilePattern)
            {
                if (f.IsMatch(file))
                    return false;
            }
            if (m_AutoIncludeDependency && !string.IsNullOrEmpty(dependentBy) && IsIncludeAsset(rootFolder, dependentBy, null))
                return true;
            bool includeFolder = false;
            foreach (var f in mIncludeFolderPattern)
            {
                if(f.IsMatch(dic))
                {
                    includeFolder = true;
                    break;
                }
            }
            if (includeFolder)
            {
                foreach (var f in mIncludeFilePattern)
                {
                    if(f.IsMatch(file))
                        return true;
                }
            }
            return false;
        }

#if UNITY_EDITOR

        static readonly Regex ARG_REGEX = new Regex(@"(\{\$[0-9]+\}|\{parent\})", RegexOptions.IgnoreCase);

        public string GetABName(MakeABNode root, string folder, string assetPath)
        {
            var pack = GetPackType(assetPath);
            // relative pattern: {$n} (n级目录)
            string packName;
            if (string.IsNullOrEmpty(m_PackageNamespace))
            {
                packName = mFallbackName;
            }
            else
            {
                packName = m_PackageNamespace;
            }
            MatchEvaluator evaluate = (match) => {
                int n;
                var str = match.Value.Substring(1, match.Length - 2);
                if(str.EqualsIgnoreCase("parent"))
                {
                    var path = assetPath;
                    var parent = MakeABNode.FindParent(root, this, ref path);
                    if (parent == null || parent.asset == null)
                        return "BASE";
                    else
                        return parent.asset.GetABName(root, parent.folder, path);
                }
                else if (str.Length > 0 && str[0] == '$' && int.TryParse(str.Substring(1), out n))
                {
                    if(folder.Length < assetPath.Length)
                    {
                        var num = 0;
                        var end = assetPath.LastIndexOf('.');
                        if (end == -1)
                            end = assetPath.Length;
                        var buf = ParallelUtils.GetBuilder();
                        var at = folder.Length;
                        if (at > 0 && folder[at - 1] == '/')
                            at -= 1;
                        while(num < n && at < end && at > 0)
                        {
                            var next = assetPath.IndexOf('/', at + 1);
                            if (next == -1)
                                next = end;
                            if (buf.Length > 0)
                                buf.Append('/');
                            if (num == 0 && next == end)
                                buf.Append("BASE");
                            else
                                buf.Append(assetPath.Substring(at + 1, next - at - 1));
                            num++;
                            at = next;
                        }
                        if (num == 0 && n > 0)
                        {
                            if (buf.Length > 0)
                                buf.Append('/');
                            buf.Append("BASE");
                        }
                        return ParallelUtils.ReturnRelease(buf);
                    }
                    else
                    {
                        return "BASE";
                    }
                }
                else
                {
                    return "BASE";
                }
            };
            packName = ARG_REGEX.Replace(packName, evaluate);

            switch (pack)
            {
                //case EPackMode.Dependent:
                //    if (packName.EndWithIgnoreCase(".dependent"))
                //        return packName;
                //    else
                //        return StringUtil.Concat(packName, ".dependent");
                //case EPackMode.Variant:
                //    var at = assetPath.IndexOf('/', folder.Length + 1);
                //    if (at > folder.Length)
                //        return StringUtil.Concat(packName, '.', assetPath.Substring(folder.Length + 1, at - folder.Length - 1));
                //    else
                //        return packName;
                case EPackMode.Scene:
                    var fname = NativeFileSystem.GetFileName(assetPath, true);
                    if (fname.EndsWithIgnoreCase(".scene"))
                        return ParallelUtils.Concat(packName, '/', fname);
                    else
                        return ParallelUtils.Concat(packName, '/', fname, ".scene");
                case EPackMode.Streaming:
                    return ParallelUtils.Concat(packName, ".streaming");
                default:
                    IAssetVariantGroup variantGrp;
                    string variant;
                    var hasVariant = AssetsUtil.VariantsForEditor.GetVariantGroup(assetPath, out variantGrp, out variant);// variantsRegex == null ? null : variantsRegex.Match(assetPath);
                    if (hasVariant)
                    {
                        var end = ParallelUtils.Concat(".", variant);
                        if (packName.EndsWithIgnoreCase(end))
                            return packName;
                        else
                            return ParallelUtils.Concat(packName, ".auto", end);
                    }
                    else
                    {
                        return packName;
                    }
            }

        }

        private void OnValidate()
        {
            //mIncludeFilePattern = null;
            //mIncludeFolderPattern = null;
            //mExcludeFilePattern = null;
            //mExcludeFolderPattern = null;
            mFallbackName = name;
        }

        public static MakeABNode ParseAssetsTree(bool displayProcess)
        {
            var cancel = displayProcess && EditorUtility.DisplayCancelableProgressBar("Processing MakeAB File", "Find MakeAB Files...", 0);
            if (cancel)
            {
                if (displayProcess)
                    EditorUtility.ClearProgressBar();
                return null;
            }
            var root = new MakeABNode();
            root.makeAbAssets = new HashSet<string>();
            var assets = AssetDatabase.FindAssets("t:MakeAB");
            SortedQueue<string> lst = new SortedQueue<string>((x) => x.Length, assets.Length);
            foreach (var guid in assets)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                lst.Add(path);
                root.makeAbAssets.Add(path);
            }
            var f = lst.Count > 0 ? (1f / lst.Count) : 1;
            for (int i = 0; i < lst.Count; i++)
            {
                cancel = displayProcess && EditorUtility.DisplayCancelableProgressBar("Processing MakeAB File", lst[i], i * f);
                if (cancel)
                {
                    if (displayProcess)
                        EditorUtility.ClearProgressBar();
                    return null;
                }
                var asset = AssetDatabase.LoadAssetAtPath<MakeAB>(lst[i]);
                if (asset == null || asset.m_Ignored)
                    continue;
                asset.Prepare(asset.name);
                var node = MakeABNode.FindNode(root, lst[i]);
                if (node != null)
                {
                    if (node.children == null)
                        node.children = new List<MakeABNode>();
                    var next = new MakeABNode(lst[i]);
                    Debug.AssertFormat(!node.HasChildFolder(next.folder), "\"{0}\"文件夹下存在多个 MakeAB 定义", node.folder);
                    next.asset = asset;
                    if (next.asset != null)
                    {
                        next.asset.mFallbackName = next.asset.name;
                        node.children.Add(next);
                    }
                    if (asset.IsUpmAssets)
                        root.upmAsset = next;
                }
            }
            if (displayProcess)
                EditorUtility.ClearProgressBar();
            return root;
        }

#endif
    }

}